#pragma once

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <opencv2/core.hpp>
#include "render.h"

class GLRender : public Render {
public:
	static GLRender* Build(const Intrinsics& intrinsic);
	~GLRender();

	bool AddBody(Body* body_ptr) override;
	bool DeleteBody(Body* body_ptr) override;
	void ClearBodies();

	bool RenderNormal() override;
	bool RenderSilhouette() override;

	float Depth(ushort depth_image_value) const override;
	float Depth(const cv::Point2i& image_coordinate) const override;
	Eigen::Vector3f PointVector(const cv::Point2i& image_coordinate) const override;

	bool FetchDepthImage() override;
	bool FetchNormalImage() override;
	bool FetchSilhouetteImage() override;

private:
	void CreateBufferObjects();
	void DeleteBufferObjects();
	
	void CalculateProjectionMatrix();
	void CalculateProjectionTerms();
	
	bool RenderNormal(
		const Eigen::Matrix4f& projection_matrix,
		const Pose& world2camera_pose);
	bool RenderSilhouette(
		const Eigen::Matrix4f& projection_matrix,
		const Pose& world2camera_pose);
	
	bool FetchDepthImage(cv::Mat* depth_image);
	bool FetchNormalImage(cv::Mat *normal_image);
	bool FetchSilhouetteImage(cv::Mat* silhouette_image);

	static int n_instances_;

	GLFWwindow* window_ = nullptr;
	bool set_up_ = false;

	unsigned fbo_ = 0;
	unsigned rbo_depth_ = 0;
	unsigned rbo_normal_ = 0;
	unsigned rbo_silhouette_ = 0;

	unsigned normal_shader_ = 0;
	unsigned silhouette_shader_ = 0;
	
	bool geometry_enable_culling_ = true;

	struct RenderBody {
		Body* body_ptr = nullptr;
		GLuint vao = 0;
		GLuint vbo = 0;
		unsigned n_vertices = 0;

		void CreateVertexObjects();
		void DeleteVertexObjects();
	};
	std::vector<RenderBody> render_bodies_;

	int image_width_, image_height_;

	bool image_rendered_ = false;
	bool depth_image_fetched_ = false;
	bool normal_image_fetched_ = false;
	bool silhouette_image_fetched_ = false;
	
	Eigen::Matrix4f projection_matrix_;
	float projection_term_a_ = 0;
	float projection_term_b_ = 0;

  float z_min_ = 0.02f;
  float z_max_ = 10.0f;
};